<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Set和Map</title>
</head>
<body>
<script type="text/javascript">
    {
        /**
         * Set:类似于数组，但是成员的值都是唯一的，没有重复的值。
         * 本身是一个构造函数，用来生成Set数据结构。
         */
        const set = new Set();
        [2, 3, 5, 4, 5, 2, 2].map(value => set.add(value));
        console.log(...set); // 2 3 5 4

        /*Set函数可以接受一个数组(或类似数组的对象)作为参数，用来初始化*/
        const set1 = new Set([1, 2, 3, 4, 4]);
        console.log(...set1); // 1 2 3 4
        console.log(set1.size); // 4

        /*数组去重*/
        const array = [1, 2, 3, 4, 4];
        console.log(new Set(array)); // {1, 2, 3, 4}

        /*
         * 向Set加入值的时候，不会发生类型转换，所以5和"5"是两个不同的值。
         * Set内部判断两个值是否不同，使用的算法叫做“Same-value equality”，它类似于精确相等运算符(===)，主要的区别是NaN等于自身，而精确相等运算符认为NaN不等于自身。
         */
        let set2 = new Set();
        set2.add(NaN);
        set2.add(NaN);
        console.log(set2); // Set(1) {NaN}, 在Set内部，两个NaN是相等
        set2.add({});
        set2.add({});
        console.log(set2); // Set(3) {NaN, {}, {}}, 由于两个空对象不相等，所以它们被视为两个值
    }
    {
        /**Set实例的属性和方法*/
        /*
         * 属性
         * Set.prototype.constructor：构造函数，默认就是Set函数。
         * Set.prototype.size：返回Set实例的成员总数。
         */
        /*
         * 操作方法
         * add(value)：添加某个值，返回Set结构本身
         * delete(value)：删除某个值，返回一个布尔值，表示删除是否成功
         * has(value)：返回一个布尔值，表示该值是否为Set的成员
         * clear()：清除所有成员，没有返回值
         */
        let list = new Set(['add', 'delete', 'has']);
        list.add('clear');
        console.log(list.has('add')); // true
        console.log(list.delete('add')); // true
        list.clear();
        console.log(list); // {}

        /*判断是否包括一个键上面，Object结构和Set结构的写法*/
        let obj = {
            'width': 1,
            'height': 1
        };
        if (obj['width']) {
            console.log(obj['width']); // 1
        }

        let set = new Set().add('width').add('height');
        if (set.has('width')) {
            console.log('width'); // width
        }
        /*Array.from方法可以将Set结构转为数组*/
        let items = new Set([1, 2, 3, 4, 5]);
        let array = Array.from(items);
        console.log(array); // [1, 2, 3, 4, 5]

        /*去除数组重复成员*/
        function dedupe(array) {
            return Array.from(new Set(array));
        }

        console.log(dedupe([1, 1, 2, 3])); // [1, 2, 3]
    }
    {
        /**
         * 遍历
         * keys()：返回键名的遍历器
         * values()：返回键值的遍历器
         * entries()：返回键值对的遍历器
         * forEach()：使用回调函数遍历每个成员
         * Set的遍历顺序就是插入顺序。这个特性有时非常有用，比如使用Set保存一个回调函数列表，调用时就能保证按照添加顺序调用。
         */
        /*1、Set结构没有键名，只有键值(或者说键名和键值是同一个值)，所以key方法和value方法的行为完全一致。*/
        let set = new Set(['red', 'green', 'blue']);
        for (let item of set.keys()) {
            console.log(item); // red green blue
        }
        for (let item of set.values()) {
            console.log(item); // red green blue
        }
        for (const item of set.entries()) {
            console.log(item); // ["red", "red"] ["green", "green"] ["blue", "blue"]
        }
        console.log(Set.prototype[Symbol.iterator] === Set.prototype.values); // true

        /*可以省略values方法，直接用for...of循环遍历Set*/
        for (const item of set) {
            console.log(item); // red green blue
        }

        /*2、forEach()*/
        new Set([1, 2, 3]).forEach((key, value) => console.log(value * 2)); // 2 4 6

        /*3、应用：扩展运算符(...)内部使用for...of循环，所以也可以用于Set结构*/
        console.log([...set]); // ["red", "green", "blue"]

        /*扩展运算符和Set结构相结合，就可以去除数组的重复成员*/
        let arr = [3, 5, 2, 2, 5, 5];
        let unique = [...new Set(arr)];
        console.log(unique); // [3, 5, 2]

        /*数组的map和filter方法也可以用于Set了*/
        console.log(unique.map(x => x * 2)); // [6, 10, 4]
        console.log(new Set(unique.filter(x => (x % 2) === 0))); // {2}

        /*使用Set可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)*/
        let a = new Set([1, 2, 3]);
        let b = new Set([4, 3, 2]);
        let union = new Set([...a, ...b]);
        console.log(union); // {1, 2, 3, 4}
        let intersect = new Set([...a].filter(x => b.has(x)));
        console.log(intersect); // {2, 3}
        let difference = new Set([...a].filter(x => !b.has(x)));
        console.log(difference); // {1}

        /*如果想在遍历操作中，同步改变原来的Set结构，目前没有直接的方法，但有两种变通方法。一种是利用原Set结构映射出一个新的结构，然后赋值给原来的Set结构；另一种是利用Array.from方法*/
        console.log(new Set([...a].map(x => x * 2))); // {2, 4, 6}
        console.log(new Set(Array.from(a, val => val * 2))); // {2, 4, 6}
    }
    {
        /**
         * WeakSet：与Set类似，也是不重复的值的集合
         * 与Set的区别：
         * 1、WeakSet的成员只能是对象，而不能是其他类型的值。
         * 2、WeakSet中的对象都是弱引用，即垃圾回收机制不考虑WeakSet对该对象的引用，也就是说，如果其他对象都不再引用该对象，那么垃圾回收机制会自动回收该对象所占用的内存，不考虑该对象还存在于WeakSet之中。这个特点意味着，无法引用WeakSet的成员，因此WeakSet是不可遍历的。
         */
        let weakSet = new WeakSet();
        const arr = [[1, 2], [3, 4]];
        weakSet.add(arr);
        console.log(weakSet); // {Array(2)}

        /*
        * 方法
        * WeakSet.prototype.add(value)：向WeakSet实例添加一个新成员。
        * WeakSet.prototype.delete(value)：清除WeakSet实例的指定成员。
        * WeakSet.prototype.has(value)：返回一个布尔值，表示某个值是否在WeakSet实例之中。
        * WeakSet不能遍历，是因为成员都是弱引用，随时可能消失，遍历机制无法保证成员的存在，很可能刚刚遍历结束，成员就取不到了。WeakSet的一个用处，是储存DOM节点，而不用担心这些节点从文档移除时，会引发内存泄漏。
        */
        weakSet.add(window);
        console.log(weakSet.has(window)); // true
        weakSet.delete(window);
        console.log(weakSet.has(window)); // false
    }
</script>
</body>
</html>